﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Reflection;
using System.Resources;
using System.Threading;
using System.Windows;
using RaisingForce.Raise.Presentation.Localization.Xaml;

namespace RaisingForce.Raise.Presentation.Localization
{
    /// <summary>
    /// Represents a class that manages the localication features.
    /// </summary>
    public static class LocalizationManager
    {
        #region Properties

        /// <summary>
        /// The <see cref="ResourceManager"/> by which resources as accessed.
        /// </summary>
        private static ResourceManager _resourceManager;

        /// <summary>
        /// True if an attempt to load the resource manager has been made.
        /// </summary>
        private static bool _resourceManagerLoaded;

        /// <summary>
        /// Gets or sets the resource manager to use to access the resources.
        /// </summary>
        public static ResourceManager ResourceManager
        {
            get
            {
                if (_resourceManager == null && !_resourceManagerLoaded)
                {
                    _resourceManager = GetResourceManager();
                    _resourceManagerLoaded = true;
                }
                return _resourceManager;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                Contract.EndContractBlock();
                _resourceManager = value;
                UpdateLocalizations();
            }
        }

        private static CultureInfo _UICulture =
            Thread.CurrentThread.CurrentUICulture;

        /// <summary>
        /// Gets or sets the current UI culture.
        /// </summary>
        /// <remarks>
        /// This property changes the UI culture of the current thread to the specified value
        /// and updates all localized property to reflect values of the new culture.
        /// </remarks>
        public static CultureInfo UICulture
        {
            get
            {
                Contract.Ensures(Contract.Result<CultureInfo>() != null);
                Contract.Assume(_UICulture != null);
                return _UICulture;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                Contract.EndContractBlock();
                _UICulture = value;
                Thread.CurrentThread.CurrentUICulture = value;
                UpdateLocalizations();
            }
        }

        #endregion

        #region Public InvokeInLocalizationContext Method

        public static void InvokeInLocalizationContext(Action action)
        {
            if (action != null)
            {
                Thread currentThread = Thread.CurrentThread;
                CultureInfo threadUICulture = currentThread.CurrentUICulture;
                Contract.Assume(threadUICulture != null);
                CultureInfo contextUICulture = LocalizationManager.UICulture;
                if (threadUICulture.Equals(contextUICulture))
                {
                    action();
                }
                else
                {
                    currentThread.CurrentUICulture = contextUICulture;
                    try
                    {
                        action();
                    }
                    finally
                    {
                        currentThread.CurrentUICulture = threadUICulture;
                    }
                }
            }
        }

        #endregion

        #region Internal AddLocalization Method

        /// <summary>
        /// Holds the list of localization instances.
        /// </summary>
        /// <remarks>
        /// <see cref="WeakReference"/> cannot be used as a localization instance
        /// will be garbage collected on the next garbage collection
        /// as the localized object does not hold reference to it.
        /// </remarks>
        private static List<LocExtension> _localizations = new List<LocExtension>();

        /// <summary>
        /// Holds the number of localizations added since the last purge of localizations.
        /// </summary>
        private static int _localizationPurgeCount;

        /// <summary>
        /// Adds the specified localization instance to the list of manages localization instances.
        /// </summary>
        /// <param name="localization">The localization instance.</param>
        internal static void AddLocalization(LocExtension localization)
        {
            if (localization == null)
            {
                throw new ArgumentNullException("localization");
            }
            Contract.EndContractBlock();
            if (_localizationPurgeCount > 50)
            {
                // It's really faster to fill a new list instead of removing elements
                // from the existing list when there are a lot of elements to remove.
                var localizations = new List<LocExtension>(_localizations.Count);
                foreach (LocExtension item in _localizations)
                {
                    if (item.IsAlive)
                    {
                        localizations.Add(item);
                    }
                }
                _localizations = localizations;
                _localizationPurgeCount = 0;
            }
            _localizations.Add(localization);
            _localizationPurgeCount += 1;
        }

        #endregion

        #region Private Methods

        private static bool isDesignAssembly(Assembly assembly)
        {
            return assembly == null ?
                false :
                string.Compare(
                    assembly.GetName().Name,
                    "Blend",
                    StringComparison.InvariantCultureIgnoreCase) == 0;
        }

        /// <summary>
        /// Returns resource manager to access the resources the application's main assembly.
        /// </summary>
        /// <returns></returns>
        private static ResourceManager GetResourceManager()
        {
            Assembly assembly = Assembly.GetEntryAssembly();
            // Check if the assembly is the "Expression Blend" executable
            if (assembly != null && isDesignAssembly(assembly))
            {
                assembly = null;
            }
            if (assembly == null)
            {
                // Design time
                // Try to find the main assembly
                foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies())
                {
                    // Check if the assembly is executable
                    if (item.EntryPoint != null)
                    {
                        // Check if the assembly contains WPF application (e.g. MyApplication.App class
                        // that derives from System.Windows.Application)
                        string appTypeName =
                            AssemblyLocalizationInfoAttribute.FindAppLocation(item);
                        Type applicationType = item.GetType(appTypeName, false);
                        if (applicationType != null
                            && typeof(Application).IsAssignableFrom(applicationType))
                        {
                            // Check if the assembly is not the "Expression Blend" executable
                            if (!isDesignAssembly(item))
                            {
                                assembly = item;
                                break;
                            }
                        }
                    }
                }
            }
            if (assembly != null)
            {
                try
                {
                    string resourcesLocation =
                        AssemblyLocalizationInfoAttribute.FindResourcesLocation(assembly);
                    return new ResourceManager(resourcesLocation, assembly);
                }
                catch (MissingManifestResourceException)
                {
                    // The resoures cannot be found in the manifest of the assembly
                }
            }
            return null;
        }

        /// <summary>
        /// Updates the localizations.
        /// </summary>
        private static void UpdateLocalizations()
        {
            foreach (LocExtension item in _localizations)
            {
                Contract.Assume(item != null);
                item.UpdateTargetValue();
            }
        }

        #endregion
    }
}
